// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::PathBuf;

use anyhow::Context;
use clap::Parser;
use log::error;
use petgraph::graph::NodeIndex;

use crate::{build::BuildContext, commands::{resolve_stats, split_stat_specs, BuildGraphArgs}, database::deploy_stat::{DeployStat, DeployStats}, deploy::undeploy_pkgs, utils::confirm, Arch, FerriumCommand};

#[derive(Debug, Parser)]
pub struct UndeployCommand {
    /// Pkg spec strings, pkg hash spec strings, or specs config paths
    specs: Vec<String>,
    /// The root path to deploy
    #[arg(long)]
    root: PathBuf,
    /// Specify the base toolchain
    #[arg(long)]
    toolchain: Option<String>,
    /// The target architecture
    #[arg(long)]
    arch: Option<Arch>,
    /// Remove all deployed packages and the deploy directory
    #[arg(short, long)]
    all: bool,
}

impl FerriumCommand for UndeployCommand {
    async fn run(self, _multi_progress: indicatif::MultiProgress) -> anyhow::Result<()> {
        let root = std::path::absolute(&self.root)
            .context(format!("failed to absolute '{}'", self.root.display()))?;
        if self.all {
            tokio::fs::remove_dir_all(DeployStats::deploy_stat_dir(&root)).await
                .context("failed to remove deploy stat dir")?;
            tokio::fs::remove_dir_all(&root).await
                .context("failed to undeploy")?;
        } else {
            let (pkg_specs, stat_specs) = split_stat_specs(self.specs);
            let (mut build_dep_graph, _, _) = BuildGraphArgs {
                specs: pkg_specs,
                toolchain: self.toolchain,
            }.build_graph().await?;
            let sorted_ids = build_dep_graph.verify(true)
                .context("failed to resolve specs")?;

            let hashes = BuildContext::spec_hashes(&build_dep_graph, &sorted_ids).await?;
            let spec_strings = BuildContext::prepare_spec_strings(&hashes, &build_dep_graph);

            let Some((deploy_stats, _)) = DeployStats::load_current_by_root(&root).await? else {
                return Err(anyhow::anyhow!("no deploy_stats found in {:?}", &root));
            };
            let remain_pkgs = deploy_stats.deployed_pkgs();
            let mut pkg_not_found = false;
            for spec_str in build_dep_graph.id_map.specs().map(|id| spec_strings[id.index()].as_str()) {
                if !remain_pkgs.contains(spec_str) {
                    error!("deployed pkg '{}' not found in deploy stats", spec_str);
                    pkg_not_found = true;
                }
            }
            if pkg_not_found && !confirm("Do you want to continue?").context("failed to read input")? {
                return Ok(());
            }
    
            let stat_dep_graph = resolve_stats(
                stat_specs.iter().map(|s| s.as_str())
                    .chain(build_dep_graph.id_map.specs().map(|id| spec_strings[id.index()].as_str())),
            ).await?;
            
            let to_undeploy = stat_dep_graph.collect_runtime_dependants(
                stat_dep_graph.specs_range.clone().map(NodeIndex::new).collect()
            );
            let arch = self.arch.unwrap_or(Arch::host());

            undeploy_pkgs(&deploy_stats,
                &root,
                to_undeploy.ones().map(|id| stat_dep_graph.node(NodeIndex::new(id)).unwrap().as_str()),
                arch
            ).await.context("failed to undeploy pkgs")?;

            let deploy_stat = DeployStat {
                mode: None,
                arch,
                pkgs: to_undeploy.ones()
                    .map(|id| stat_dep_graph.node(NodeIndex::new(id)).unwrap().clone())
                    .filter(|pkg| remain_pkgs.contains(pkg.as_str()))
                    .collect(),
            };
            deploy_stat.save_after_current(root).await
                .context("failed to save deploy stat")?;
        }
        Ok(())
    }
}
